semget関数は、既存セマフォ集合のセマフォIDあるいは、新規作成したセマフォ集合のセマフォIDを取得します。セマフォとは、元々は「手旗信号」の意味で、それから派生した鉄道の腕木信号に由来します。これにより、プロセス間の待ち合わせと排他制御を行うことができます。
セマフォを操作する関数にはsemget関数以外に、semctl関数とsemop関数があります。プロセス間の待ち合わせと、排他制御の手順についてはsemop関数をご覧ください。
セマフォは、次の手順で操作します。
- semget関数でセマフォ集合のセマフォIDを取得します。なお、セマフォ集合は新規作成もできます。
- semctl関数でセマフォに初期値を設定します。
- それぞれのプロセスでsemop関数でセマフォのロック/アンロックを行い、プロセス間の待ち合わせや排他制御を行います。
- semctl関数でマフォ集合を削除します。
この関数は、C言語のライブラリ関数(標準関数)ではありませんので、コンパイラにより、使えない場合があります。
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
keyは取得するセマフォ集合のキーを指定します。
nsemsはセマフォの数を指定します。
semflgはオプションを指定します。
戻り値として、処理が成功した場合は、セマフォIDが、失敗した場合は-1を返します。なお、セマフォIDは正の整数値です。
新規にセマフォ集合を作成するには、次の2つの方法があります。なお、セマフォ集合内に作成するセマフォの数は第2引数のnsemsで指定します。
- 第1引数のkeyにIPC_PRIVATEを指定します。
- 第1引数のkeyにユニークな値を指定し、第3引数のsemflgにIPC_CREATを指定します。なお、semflgにIPC_CREATとIPC_EXCLを指定すると、keyに対するセマフォ集合が既に存在していた場合にエラーになります。
第3引数のsemflgの下位9ビットは、そのセマフォの所有者、グループ、他人に対するアクセス許可の定義として使用します。
次の例題プログラムは、3つの子プロセスによるファイル出力を排他制御しています。
プログラム 例
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include <sys/sem.h> #define LOCK -1 #define UNLOCK 1 /* セマフォ操作(ロック/アンロック) */ void MySemop(int SemId, int Op); int main() { FILE *fp; int semid; union semun { int val; /* SETVAL の値 */ struct semid_ds *buf; /* IPC_STAT, IPC_SET 用のバッファ */ unsigned short *array; /* GETALL, SETALL 用の配列 */ } ctl_arg; char *file = './sem.txt'; int child_cnt; int line_cnt; if ((fp = fopen(file, 'w')) == NULL) { perror('main : fopen '); exit(EXIT_FAILURE); } setvbuf(fp, NULL, _IONBF, 0); /* 1つのセマフォを持つセマフォ集合を新規作成 */ if ((semid = semget(IPC_PRIVATE, 1, 0600)) == -1){ perror('main : semget '); exit(EXIT_FAILURE); } /* セマフォに初期値(1)を設定 */ ctl_arg.val = 1; if (semctl(semid, 0, SETVAL, ctl_arg) == -1){ perror('main : semctl '); exit(EXIT_FAILURE); } /* 子プロセスを3個生成 */ for (child_cnt = 0; child_cnt < 3; ++child_cnt) { if (fork() == 0) { /* 子プロセス */ printf('子プロセス%d開始\n', child_cnt + 1); MySemop(semid, LOCK); /* ロック */ for (line_cnt = 1; line_cnt <= 5; ++line_cnt) { fprintf(fp, '子プロセス%dのメッセージ%d\n', child_cnt + 1, line_cnt); sleep(1); } MySemop(semid, UNLOCK); /* アンロック */ printf('子プロセス%d終了\n', child_cnt + 1); exit(EXIT_SUCCESS); } } /* 親プロセス。子プロセスの終了を待つ */ for (child_cnt = 0; child_cnt < 3; ++child_cnt) { wait(NULL); } /* セマフォを削除 */ if (semctl(semid, 0, IPC_RMID, ctl_arg) == -1){ perror('main : semctl '); exit(EXIT_FAILURE); } fclose(fp); printf('親プロセス終了\n'); return EXIT_SUCCESS; } /* セマフォ操作(ロック/アンロック) */ void MySemop(int p_semid, int p_op) { struct sembuf sops[1]; sops[0].sem_num = 0; /* セマフォ番号 */ sops[0].sem_op = p_op; /* セマフォ操作 */ sops[0].sem_flg = 0; /* 操作フラグ */ if (semop(p_semid, sops, 1) == -1) { perror('MySemop '); exit(EXIT_FAILURE); } return; }
例の実行結果
$ ./semget.exe 子プロセス1開始 子プロセス2開始 子プロセス3開始 子プロセス1終了 子プロセス2終了 子プロセス3終了 親プロセス終了 $ $ cat sem.txt 子プロセス1のメッセージ1 子プロセス1のメッセージ2 子プロセス1のメッセージ3 子プロセス1のメッセージ4 子プロセス1のメッセージ5 子プロセス2のメッセージ1 子プロセス2のメッセージ2 子プロセス2のメッセージ3 子プロセス2のメッセージ4 子プロセス2のメッセージ5 子プロセス3のメッセージ1 子プロセス3のメッセージ2 子プロセス3のメッセージ3 子プロセス3のメッセージ4 子プロセス3のメッセージ5 $